/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*********************************************************************************************
    *   Filename        : lrc_hw.c

    *   Description     :

    *   Author          : Bingquan

    *   Email           : bingquan_cai@zh-jieli.com

    *   Last modifiled  : 2018-11-01 15:45

    *   Copyright:(c)JIELI  2011-2017  @ , All Rights Reserved.
*********************************************************************************************/
#include "lrc_hw.h"
#include "asm/includes.h"
#include "power_hw.h"
#include "gpio.h"
#include "power_wakeup.h"
#include "system/includes.h"

#define LOG_TAG_CONST       LRC
#define LOG_TAG             "[LRC]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"

#define IRQ_FUNCTION_PROTOTYPE(idx, hdl) \
    __attribute__((interrupt("")))

#define IRQ_REGISTER(idx, hdl, prio) \
    request_irq(idx, prio, hdl, 0)
/**
 * @brief Low Power Debug Signal(mapping to DSview)
 */

#define     APP_IO_DEBUG_0(i, x)       // {JL_PORT##i->DIR &= ~BIT(x), JL_PORT##i->OUT &= ~BIT(x);}
#define     APP_IO_DEBUG_1(i, x)       // {JL_PORT##i->DIR &= ~BIT(x), JL_PORT##i->OUT |= BIT(x);}

/* ----------------------------------------------------------- */
static struct lrc_driver_hdl lrc;

#define __this  (&lrc)

#define PLL_CLK_Hz      480000000L

void __hw_set_osc_hz(u32 fre);

/*
 *                   HW LRC 32K
 */

AT_VOLATILE_RAM_CODE
u32 __get_lrc_hz(void)
{
    log_info("(AVG)LRC %d(Hz )", __this->avg_lrc_hz);

    return __this->avg_lrc_hz;
}
AT_VOLATILE_RAM_CODE
u32 __get_lrc_last_hz(void)
{
    log_info("LRC %d(Hz )", __this->lrc_hz);

    return __this->lrc_hz;
}
static void LRC_CLK_EN(void)
{
    // LRC init
    p33_tx_1byte(P3_LRC_CON0,   LRC_CON0_INIT);
    p33_tx_1byte(P3_LRC_CON1,   LRC_CON1_INIT);
}

static void __hw_lrc_enable(u8 en)
{
    if (__this->enable == 0) {
        return;
    }

    JL_LRCT->CON &= ~BIT(0);

    JL_LRCT->CON |= BIT(6);    // clr pending

    if (en) {
        JL_LRCT->CON |= BIT(0);   // enable
    }
}

static void __lrc_averge_filter(u32 val)
{
    u8 i = 0, k = 0;
    u32 sum = 0;

    k = __this->sample_point;

    if (__this->fifo_arr[0] == 0) {
        for (i = 0; i < k; i++) {
            __this->fifo_arr[i] = val;
        }
    } else {
        for (i = 0; i < k - 1; i++) {
            __this->fifo_arr[i] = __this->fifo_arr[i + 1];
        }
        __this->fifo_arr[k - 1] = val;
    }

    for (i = 0; i < k; i++) {
        sum +=    __this->fifo_arr[i];
    }

    __this->avg_lrc_hz = sum / k;
}

static void lrc_timeout_handler(void *p)
{
    u32 pll_cnt;

    if ((JL_LRCT->CON & BIT(0)) == 0) {
        return;
    }

    while ((JL_LRCT->CON & BIT(7)) == 0);

    APP_IO_DEBUG_1(A, 2);

    pll_cnt = JL_LRCT->NUM;

    // Stop LRC
    __hw_lrc_enable(0);

    // Filte out invalid result
    if (pll_cnt == 0) {
        JL_LRCT->CON |= BIT(0);   // enable
        return;
    }

    // Calaculate LRC
    __this->lrc_hz = (u32)(((u64)(((u64)32 << __this->cnt) * (u64)PLL_CLK_Hz)) / ((u64)pll_cnt));

    log_info("LRC %d(Hz )", __this->lrc_hz);

    // Average filter
    __lrc_averge_filter(__this->lrc_hz);

    log_debug("(AVG)LRC %d(Hz )", __this->avg_lrc_hz);

    if (__this->sample_cnt <= __this->sample_point) {
        __this->sample_cnt++;
        log_info("cnt %d", __this->sample_cnt);

        // Sample done
        if (__this->sample_cnt == __this->sample_point) {
            log_info("--->Sample Done");

            usr_timer_modify(__this->timer, 1000);
        }
    }

    __hw_lrc_enable(1);

    APP_IO_DEBUG_0(A, 2);
}

static void lrc_critical_enter(void)
{
    __hw_lrc_enable(0);
}

static void lrc_critical_exit(void)
{
    __hw_lrc_enable(1);
}

CLOCK_CRITICAL_HANDLE_REG(lrc, lrc_critical_enter, lrc_critical_exit)
extern const int  clock_sys_src_use_lrc_hw ; // 当使用lrc时timer.c需要特殊设置
void __hw_lrc_init(void)
{
    if (clock_sys_src_use_lrc_hw) {
        u32 efuse_page1_val = p33_rd_page(1);
        u32 diff = (efuse_page1_val >> 22) & 0x3ff;
        if (diff != 0x3ff) { // trim 过
            efuse_page1_val = JL_CLOCK->PLL_CON1 & 0xfff;
            __this->avg_lrc_hz = 480000000L / (efuse_page1_val + 2) / 8;
            __this->enable = 1;
            log_info("LRC first trace used trim %d", __this->avg_lrc_hz);
            return;
        }
    }

    LRC_CLK_EN();

    __this->enable = 1;

    __this->cnt = 0;
    log_info("LRC 2^%d", __this->cnt);

    __this->sample_cnt = 0;

    __this->sample_point = 20;

    LRCT_TS(__this->cnt);

    __hw_lrc_enable(1);

    lrc_timeout_handler(NULL);

    log_info("LRC first trace %d", __this->avg_lrc_hz);

    __this->timer = usr_timer_add(NULL, lrc_timeout_handler, 10, 0);
}

bool __lrc_trace_ready()
{
    return (__this->sample_cnt >= __this->sample_point) ? TRUE : FALSE;
}

s32 low_power_trace_drift(u32 usec)
{
#ifdef CONFIG_FPGA_ENABLE
    return 0;
#endif

    if (__this->enable == 0) {
        return 0;
    }

    u32 result = __this->lrc_hz;

    u32 oschz = __this->avg_lrc_hz;

    u32 total_msec = ((u64)usec * oschz / (32000L << __this->cnt)) / 1000L;

    // PLL_CNT = LRC PRD * LRC CNT / LRC clk * PLL clk

    s32 diff_cnt = (s32)((1000000000L / result) - (1000000000L / oschz));

    s32 drift  = ((s32)(diff_cnt * total_msec) / 1000L);
    drift -= ((usec) / 12500) - 2.5;      // [Attention]DC value create by IC

    return drift;
}

